package com.chenju.javastatic.service.impl;

import com.chenju.javastatic.common.enums.EasyCode;
import com.chenju.javastatic.domain.pojo.Neo4jG6ResultsPoJo;
import com.chenju.javastatic.domain.pojo.Neo4jResultsPoJo;
import com.chenju.javastatic.service.Neo4JService;
import com.chenju.javastatic.utils.EasyListUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.neo4j.driver.*;
import org.neo4j.driver.internal.InternalNode;
import org.neo4j.driver.internal.InternalPath;
import org.neo4j.driver.internal.InternalRelationship;
import org.neo4j.driver.types.Node;
import org.neo4j.driver.types.Relationship;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;

@Service
public class Neo4JServiceImplements implements Neo4JService {
    private static final Logger logger = LoggerFactory.getLogger(Neo4JServiceImplements.class);

    private final Driver driver;

    public Neo4JServiceImplements(Driver driver) {
        this.driver = driver;
    }

    @Override
    public void batchInsert() {
        // 初始化数据库
        init();

        // 插入class
        batchInsertClassNode();

        // 插入method及method与class的关系
        batchInsertMethodNode();

        // 插入annotation
        batchInsertAnnotationNode();

        // TODO: 插入parameter

        // 插入方法间的调用关系
        batchInsertMethodsRelation();

        // 插入接口与方法的关系
        batchInsertInterfaceNode();

        // 插入Feign调用关系
        batchInsertFeignCall();
    }

    @Override
    public Neo4jResultsPoJo excute(String cypherSQL) {
        return formatRecords(executeQuery(cypherSQL));

    }

    @Override
    public Neo4jG6ResultsPoJo excuteG6(String cypherSQL) {
        return formatG6Records(executeQuery(cypherSQL));
    }

    public Neo4jG6ResultsPoJo formatG6Records(List<Map<String, Object>> maps) {
        Neo4jG6ResultsPoJo neo4jG6ResultsPoJo = new Neo4jG6ResultsPoJo();
        Set<Map<String, Object>> nodes = new HashSet<>();
        Set<Map<String, Object>> edges = new HashSet<>();

        if (maps != null && !maps.isEmpty()) {
            for (Map<String, Object> r : maps) {
                for (Map.Entry<String, Object> entry : r.entrySet()) {
                    Object value = entry.getValue();
                    if (value instanceof InternalNode) {
                        // 如果是节点
                        nodes.add(nodeToG6Map((Node) value));
                    } else if (value instanceof InternalPath) {
                        // 如果是关系
                        InternalPath path = (InternalPath) value;
                        for (Relationship ship : path.relationships()) {
                            edges.add(shipToG6Map(ship));
                        }
                        for (Node node : path.nodes()) {
                            nodes.add(nodeToG6Map(node));
                        }
                    } else if (value instanceof InternalRelationship) {
                        edges.add(shipToG6Map((Relationship) value));
                    }
                }
            }
        }

        neo4jG6ResultsPoJo.setNodes(nodes);
        neo4jG6ResultsPoJo.setEdges(edges);

        return neo4jG6ResultsPoJo;
    }

    public Neo4jResultsPoJo formatRecords(List<Map<String, Object>> maps) {
        Neo4jResultsPoJo neo4jResultsPoJo = new Neo4jResultsPoJo();
        Map<String, Object> graph = new HashMap<>();
        List<Map<String, Object>> data = new ArrayList<>();
        Map<String, Object> dataMap = new HashMap<>();
        Set<Map<String, Object>> nodes = new HashSet<>();
        Set<Map<String, Object>> relationships = new HashSet<>();
        Map<String, Object> resultMap = new HashMap<>();
        List<Map<String, Object>> results = new ArrayList<>();

        if (maps != null && !maps.isEmpty()) {
            for (Map<String, Object> r : maps) {
                for (Map.Entry<String, Object> entry : r.entrySet()) {
                    Object value = entry.getValue();
                    if (value instanceof InternalNode) {
                        // 如果是节点
                        nodes.add(internalNodeToMap((InternalNode) value));
                    } else if (value instanceof InternalPath) {
                        // 如果是关系
                        InternalPath path = (InternalPath) value;
                        for (Relationship ship : path.relationships()) {
                            relationships.add(shipToMap(ship));
                        }
                        for (Node node : path.nodes()) {
                            nodes.add(nodeToMap(node));
                        }
                    } else if (value instanceof InternalRelationship) {
                        relationships.add(shipToMap((Relationship) value));
                    }
                }
            }
        }

        graph.put("nodes", nodes);
        graph.put("relationships", relationships);

        dataMap.put("graph", graph);

        data.add(dataMap);

        resultMap.put("columns", EasyListUtils.add(new ArrayList<>(), "ClassNode"));
        resultMap.put("data", data);

        results.add(resultMap);

        neo4jResultsPoJo.setResults(results);
        neo4jResultsPoJo.setErrors(new ArrayList<>());

        return neo4jResultsPoJo;
    }

    public Map<String, Object> nodeToG6Map(Node node){
        Map<String, Object> maps = new HashMap<>();
        Map<String, String> dataMap = new HashMap<>();
        maps.put("id", "node-" + node.id());
        maps.put("labels", node.labels());
        node.labels().forEach(s -> dataMap.put("category", s));
        node.asMap().forEach((key, value) -> dataMap.put(key, value.toString()));
        maps.put("data", dataMap);
        return maps;
    }

    public Map<String, Object> shipToG6Map(Relationship ship){
        Map<String, Object> maps = new HashMap<>();
        Map<String, String> dataMap = new HashMap<>();
        maps.put("id", "edge-" + ship.id());
        maps.put("source", "node-" + ship.startNodeId());
        maps.put("target", "node-" + ship.endNodeId());
        ship.asMap().forEach((key, value) -> {
            if (key.equals("type")) {
                dataMap.put("category", value.toString());
            }
            dataMap.put(key, value.toString());
        });
        System.out.println("RES:"+ ship.id()+dataMap.get("category"));
        maps.put("data", dataMap);
        return maps;
    }

    public Map<String, Object> internalNodeToMap(InternalNode node){
        Map<String, Object> maps = new HashMap<>();
        maps.put("id", node.id());
        maps.put("labels", node.labels());
        maps.put("properties", node.asMap());
        return maps;
    }

    public Map<String, Object> nodeToMap(Node node){
        Map<String, Object> maps = new HashMap<>();
        maps.put("id", node.id());
        maps.put("labels", node.labels());
        maps.put("properties", node.asMap());
        return maps;
    }

    public Map<String, Object> shipToMap(Relationship ship){
        Map<String, Object> maps = new HashMap<>();
        maps.put("id", ship.id());
        maps.put("type", ship.type());
        maps.put("startNode", ship.startNodeId());
        maps.put("endNode", ship.endNodeId());
        maps.put("properties", ship.asMap());
        return maps;
    }

    public List<Map<String, Object>> executeQuery(String query) {
        try (Session session = driver.session()) {
            return session.readTransaction(tx -> {
                Result result = tx.run(query);
                List<Map<String, Object>> resultList = new ArrayList<>();
                while (result.hasNext()) {
                    resultList.add(result.next().asMap());
                }
                return resultList;
            });
        } catch (Exception e) {
            e.printStackTrace();
            return new ArrayList<>();
        }
    }

    public void init(){
        String cypherSQL = "MATCH (m) DETACH DELETE (m)";
        batchInsert(cypherSQL);
    }

    private void batchInsertFeignCall() {
        // 插入关系
        String createRelationFromInterfaceToMethodCypherSQL = "CALL apoc.periodic.iterate(" +
                "\"CALL apoc.load.jdbc('simple', 'feigncall_entity') YIELD row\", " +
                "\"CREATE (m: InterfaceNode {requestPaths: row.requestPaths, application: row.application}) -[r:" + EasyCode.FEIGNCALL_TYPE + " {type: '" + EasyCode.FEIGNCALL_TYPE + "'}]->(n: MethodNode {id: row.method_id})\", " +
                "{batchSize: 1000, parallel: false}) " +
                "YIELD batches, total " +
                "RETURN batches, total";
        batchInsert(createRelationFromInterfaceToMethodCypherSQL);
    }

    public void batchInsertInterfaceNode(){
        String insertInterfaceCypherSQL = "CALL apoc.periodic.iterate(" +
                "\"CALL apoc.load.jdbc('simple', 'interface_entity') YIELD row\", " +
                "\"CREATE (m: InterfaceNode {id: row.id, name: row.name, requestMethods: row.request_methods, requestPaths: row.requestPaths, methodId: row.method_id})\", " +
                "{batchSize: 1000, parallel: false}) " +
                "YIELD batches, total " +
                "RETURN batches, total";
        batchInsert(insertInterfaceCypherSQL);

        // 插入关系
        String createRelationToMethodCypherSQL = "CALL apoc.periodic.iterate(" +
                "\"MATCH (m:InterfaceNode), (n:MethodNode) WHERE m.methodId=n.id RETURN m, n\", " +
                "\"CREATE (n)-[r:" + EasyCode.INTERFACED_TYPE + " {type: '" + EasyCode.INTERFACED_TYPE + "'}]->(m)\", " +
                "{batchSize:1000, iterateList:true})";
        batchInsert(createRelationToMethodCypherSQL);
    }

    public void batchInsertMethodsRelation() {
        // 插入所有调用关系除了extends
        String cypherSQL = "CALL apoc.periodic.iterate(" +
                "\"CALL apoc.load.jdbc('simple', 'methodinsn_entity') YIELD row\", " +
                "\"WITH row, row.invoked_id as invoked_id, row.method_id as method_id, row.invoke_type as invoke_type, row.direction as direction " +
                "MATCH (m:MethodNode {id: method_id}), (n:MethodNode {id: invoked_id}) WHERE direction=1 " +
                "CREATE (m)-[r: INVOKEDBY {type: invoke_type}]->(n)\", " +
                "{batchSize: 1000, parallel: false}) " +
                "YIELD batches, total " +
                "RETURN batches, total";
        batchInsert(cypherSQL);

        // 插入extends调用关系ETS
        // 插入outerClassMethod调用关系IOS
        String extendsCypherSQL = "CALL apoc.periodic.iterate(" +
                "\"CALL apoc.load.jdbc('simple', 'methodinsn_entity') YIELD row\", " +
                "\"WITH row, row.invoked_id as invoked_id, row.method_id as method_id, row.invoke_type as invoke_type, row.direction as direction " +
                "MATCH (m:MethodNode {id: invoked_id}), (n:MethodNode {id: method_id}) WHERE direction=0 " +
                "CREATE (m)-[r: INVOKEDBY {type: invoke_type}]->(n)\", " +
                "{batchSize: 1000, parallel: false}) " +
                "YIELD batches, total " +
                "RETURN batches, total";
        batchInsert(extendsCypherSQL);
    }

    public void batchInsertAnnotationNode() {
        // 插入annotation
        String insertAnnotationCypherSQL = "CALL apoc.periodic.iterate(" +
                "\"CALL apoc.load.jdbc('simple', 'annotation_entity') YIELD row\", " +
                "\"CREATE (m:AnnotationNode {id: row.id, name: row.name, valuesStr: row.values_str, foreignId: row.foreign_id, type: row.type})\", " +
                "{batchSize: 1000, parallel: false}) " +
                "YIELD batches, total " +
                "RETURN batches, total";
        batchInsert(insertAnnotationCypherSQL);

        // 插入关系
        String createRelationToClassCypherSQL = "CALL apoc.periodic.iterate(" +
                "\"MATCH (a:AnnotationNode {type: 'CLASS'}), (m:ClassNode) WHERE a.foreignId=m.id RETURN a, m\", " +
                "\"CREATE (a)-[r:" + EasyCode.ANNOTATED_TYPE + " {type: '" + EasyCode.ANNOTATED_TYPE + "'}]->(m)\", " +
                "{batchSize:1000, iterateList:true})";
        batchInsert(createRelationToClassCypherSQL);

        String createRelationToMethodCypherSQL = "CALL apoc.periodic.iterate(" +
                "\"MATCH (a:AnnotationNode {type: 'METHOD'}), (m:MethodNode) WHERE a.foreignId=m.id RETURN a, m\", " +
                "\"CREATE (a)-[r:" + EasyCode.ANNOTATED_TYPE + " {type: '" + EasyCode.ANNOTATED_TYPE + "'}]->(m)\", " +
                "{batchSize:1000, iterateList:true})";
        batchInsert(createRelationToMethodCypherSQL);
        //annotationRepository.createReleationShipToClass();
        //annotationRepository.createReleationShipToMethod();
    }

    public void batchInsertMethodNode() {
        // 插入method
        String insertMethodCypherSQL = "CALL apoc.periodic.iterate(" +
                "\"CALL apoc.load.jdbc('simple', 'method_entity') YIELD row\", " +
                "\"CREATE (m:MethodNode {id: row.id, name: row.name, descriptor: row.descriptor, className: row.class_name, classId: row.class_id})\", " +
                "{batchSize: 1000, parallel: false}) " +
                "YIELD batches, total " +
                "RETURN batches, total";
        batchInsert(insertMethodCypherSQL);

        // 插入关系
        String cypherSQL = "CALL apoc.periodic.iterate(" +
                "\"MATCH (m:ClassNode), (n:MethodNode) WHERE m.id=n.classId RETURN m, n\", " +
                "\"CREATE (m)-[r: "+EasyCode.CONTAIN_TYPE+" {type: '" + EasyCode.CONTAIN_TYPE + "'}]->(n)\", " +
                "{batchSize:1000, iterateList:true})";

        batchInsert(cypherSQL);
        //methodRepository.createRelationShipToClass();
    }

    public void batchInsertClassNode() {
        // 插入
        String cypherSQL = "CALL apoc.periodic.iterate(" +
                "\"CALL apoc.load.jdbc('simple', 'class_entity') YIELD row\", " +
                "\"CREATE (m:ClassNode {id: row.id, name: row.name, application: row.application, superName: row.super_name, interfaces: row.interfaces, isInterface: row.is_interface, isController: row.is_controller, isFeignClient: row.is_feignclient, isAspect: row.is_aspect, isConfiguration: row.is_configuration})\", " +
                "{batchSize: 1000, parallel: false}) " +
                "YIELD batches, total " +
                "RETURN batches, total";

        batchInsert(cypherSQL);
    }

    public void batchInsert(String cypherSQL) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        try (Session session = driver.session()) {
            session.writeTransaction((TransactionWork<Void>) tx -> {
                tx.run(cypherSQL);
                return null;
            });
        } catch (Exception e) {
            e.printStackTrace(); // or handle the exception as needed
        }

        stopWatch.stop();
        logger.info(String.format("执行cypherSQL耗时: %s ms\nCypherSQL: %s", stopWatch.getTime(), cypherSQL));
    }
}
